package org.lex.game.sudoku.view.newgame;

import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.Box;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTabbedPane;
import javax.swing.SpinnerNumberModel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import ms.sudoku.GeneratorOptions;
import ms.sudoku.technique.EliminationTechnique;

import org.lex.game.sudoku.SudokuApplication;
import org.lex.game.sudoku.model.DefaultSudokuModel;
import org.lex.game.sudoku.model.SudokuModel;
import org.lex.game.sudoku.model.builder.EmptySudokuBuilder;
import org.lex.game.sudoku.model.builder.IntArraySudokuBuilder;
import org.lex.game.sudoku.model.builder.MicrosoftSudokuBuilder;
import org.lex.game.sudoku.model.builder.SudokuBuilder;
import org.lex.game.sudoku.view.SudokuBoard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import common.Difficulty;
import common.Sudokus;

public class NewGameDialog extends JDialog {
	private static final long serialVersionUID = 8803316541435267021L;

	private JPanel contentPanel;
	private JLabel boxSizeLabel;
	private JSpinner boxSizeSpinner;
	private SpinnerNumberModel boxSizeSpinnerModel;
	private JTabbedPane buildersTabPane;
	private JPanel autoGeneratPane;
	private SudokuBoard mouseInputPane;
	private JPanel buttonsPane;
	private JButton okButton;
	private JButton cancelButton;
	private JLabel difficultyLabel;
	private JComboBox<Difficulty> difficultyComboBox;
	private JLabel filledCellsLabel;
	private JSpinner filledCellsSpinner;
	private SpinnerNumberModel filledCellsSpinnerModel;
	private HandInputPane handInputPane;

	private boolean canceled = false;
	private final SudokuApplication application;

	private final Logger log = LoggerFactory.getLogger(NewGameDialog.class);

	public NewGameDialog() {
		this.application = null;
		this.initialize();
	}

	/**
	 * Create the dialog.
	 */
	public NewGameDialog(SudokuApplication application) {
		super(application.getMainFrame());
		this.application = application;
		initialize();
	}

	public static SudokuBuilder getSudokuBuilder(SudokuApplication application, SudokuBuilder hints) {
		NewGameDialog d = new NewGameDialog(application);
		d.setModal(true);
		d.setLocationRelativeTo(application.getMainFrame());
		d.setHints(hints);
		d.setVisible(true);
		return d.getSudokuBuilder();
	}

	private void setHints(SudokuBuilder builder) {
		if (null != builder) {
			// box size
			if (builder instanceof ProxySudokuBuilder) {
				ProxySudokuBuilder pb = (ProxySudokuBuilder) builder;
				this.getBoxSizeSpinner().setValue(pb.getBoxSize());
			}
			// ms builder
			MicrosoftSudokuBuilder msBuilder = null;
			if (builder instanceof MicrosoftSudokuBuilder) {
				msBuilder = (MicrosoftSudokuBuilder) builder;
			}
			if (builder instanceof ProxySudokuBuilder) {
				ProxySudokuBuilder pb = (ProxySudokuBuilder) builder;
				if (pb.delegate instanceof MicrosoftSudokuBuilder) {
					msBuilder = (MicrosoftSudokuBuilder) pb.delegate;
				}
			}
			if (null != msBuilder) {
				GeneratorOptions opt = msBuilder.getGeneratorOptions();
				this.getBoxSizeSpinner().setValue(opt.getBoxSize());
				this.getDifficultyComboBox().setSelectedItem(opt.getDifficulty());
				this.getFilledCellsSpinner().setValue(opt.getMinFilledCells());
			}
		}
	}

	public SudokuBuilder getSudokuBuilder() {
		log.info("user canceled? " + this.canceled);
		if (true == this.canceled) {
			return null;
		}
		Component selectedTab = this.getBuildersTabPane().getSelectedComponent();
		int boxSize = this.getBoxSize();
		SudokuBuilder builder = null;
		if (selectedTab == this.getAutoGeneratPane()) {
			GeneratorOptions opt = GeneratorOptions.create(Difficulty.Medium);
			opt = new GeneratorOptions(boxSize, (Difficulty) this.getDifficultyComboBox().getSelectedItem(),
					this.getFilledCells(), opt.getDecisionPoints(), opt.getPuzzlesCount(), opt.getAllowedTechniques()
							.toArray(new EliminationTechnique[0]), opt.getEnsureSymmetry());
			builder = new MicrosoftSudokuBuilder(opt);
		} else if (selectedTab == this.getMouseInputPane()) {
			int[][] data = this.getMouseInputPane().getSudokuModel().toArray();
			builder = new IntArraySudokuBuilder(data);
		} else if (selectedTab == this.getHandInputPane()) {
			builder = this.getHandInputPane().getBuilder();
		} else {
			log.error("unkown selectedTab=" + selectedTab);
			throw new RuntimeException("can't handle current selected tab");
		}
		ProxySudokuBuilder proxyBuilder = new ProxySudokuBuilder(builder);
		proxyBuilder.setBoxSize(boxSize);
		return proxyBuilder;
	}

	private int getBoxSize() {
		return (Integer) this.getBoxSizeSpinner().getValue();
	}

	private int getFilledCells() {
		return (Integer) this.getFilledCellsSpinner().getValue();
	}

	private void boxSizeChanged() {
		int boxSize = getBoxSize();
		int gridSize = boxSize * boxSize;
		int cellsCount = gridSize * gridSize;
		// update filled cells spinner
		SpinnerNumberModel filledCellsSpinnerModel = this.getFilledCellsSpinnerModel();
		if (((Integer) filledCellsSpinnerModel.getValue()) > cellsCount) {
			filledCellsSpinnerModel.setValue(cellsCount / 2);
		}
		filledCellsSpinnerModel.setMaximum(cellsCount);
	}

	private void cancel() {
		canceled = true;
		dispose();
	}

	private void ok() {
		canceled = false;
		dispose();
	}

	private void initialize() {
		this.setSize(300, 400);
		this.setTitle("New Game");
		this.setContentPane(this.getContentPanel());
		this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
		this.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				cancel();
			}
		});
	}

	private JPanel getContentPanel() {
		if (contentPanel == null) {
			contentPanel = new JPanel();
			contentPanel.setBorder(new EmptyBorder(11, 11, 12, 12));
			GridBagLayout layout = new GridBagLayout();
			contentPanel.setLayout(layout);
			GridBagConstraints boxSizeLabelGridBagConstraints = new GridBagConstraints();
			boxSizeLabelGridBagConstraints.insets = new Insets(0, 0, 5, 0);
			boxSizeLabelGridBagConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			boxSizeLabelGridBagConstraints.gridx = 0;
			boxSizeLabelGridBagConstraints.gridy = 0;
			contentPanel.add(getBoxSizeLabel(), boxSizeLabelGridBagConstraints);
			GridBagConstraints boxSizeTextFieldGridBagConstraints = new GridBagConstraints();
			boxSizeTextFieldGridBagConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			boxSizeTextFieldGridBagConstraints.insets = new Insets(0, 5, 5, 0);
			boxSizeTextFieldGridBagConstraints.gridx = 1;
			boxSizeTextFieldGridBagConstraints.gridy = 0;
			contentPanel.add(getBoxSizeSpinner(), boxSizeTextFieldGridBagConstraints);
			GridBagConstraints tabbedPaneGridBagConstraints = new GridBagConstraints();
			tabbedPaneGridBagConstraints.gridwidth = 4;
			tabbedPaneGridBagConstraints.weighty = 10.0;
			tabbedPaneGridBagConstraints.weightx = 10.0;
			tabbedPaneGridBagConstraints.insets = new Insets(0, 0, 11, 5);
			tabbedPaneGridBagConstraints.fill = GridBagConstraints.BOTH;
			tabbedPaneGridBagConstraints.gridx = 0;
			tabbedPaneGridBagConstraints.gridy = 1;
			contentPanel.add(getBuildersTabPane(), tabbedPaneGridBagConstraints);
			GridBagConstraints buttonsPaneGridBagConstraints = new GridBagConstraints();
			buttonsPaneGridBagConstraints.insets = new Insets(0, 0, 0, 5);
			buttonsPaneGridBagConstraints.gridwidth = 4;
			buttonsPaneGridBagConstraints.fill = GridBagConstraints.BOTH;
			buttonsPaneGridBagConstraints.gridx = 0;
			buttonsPaneGridBagConstraints.gridy = 2;
			contentPanel.add(getButtonsPane(), buttonsPaneGridBagConstraints);
		}
		return contentPanel;
	}

	private JLabel getBoxSizeLabel() {
		if (boxSizeLabel == null) {
			boxSizeLabel = new JLabel("BoxSize");
		}
		return boxSizeLabel;
	}

	private JSpinner getBoxSizeSpinner() {
		if (boxSizeSpinner == null) {
			boxSizeSpinner = new JSpinner(this.getBoxSizeSpinnerModel());
			boxSizeSpinner.setEnabled(false);
			boxSizeSpinner.addChangeListener(new ChangeListener() {
				@Override
				public void stateChanged(ChangeEvent e) {
					boxSizeChanged();
				}
			});
		}
		return boxSizeSpinner;
	}

	private SpinnerNumberModel getBoxSizeSpinnerModel() {
		if (null == boxSizeSpinnerModel) {
			boxSizeSpinnerModel = new SpinnerNumberModel(3, 1, Sudokus.MAX_BOX_SIZE, 1);
		}
		return boxSizeSpinnerModel;
	}

	private JTabbedPane getBuildersTabPane() {
		if (buildersTabPane == null) {
			buildersTabPane = new JTabbedPane(JTabbedPane.TOP);
			buildersTabPane.addTab("Random", null, getAutoGeneratPane(), null);
			buildersTabPane.addTab("Mouse Input", null, getMouseInputPane(), null);
			buildersTabPane.addTab("Hand Input", null, getHandInputPane(), null);
		}
		return buildersTabPane;
	}

	private JPanel getAutoGeneratPane() {
		if (autoGeneratPane == null) {
			autoGeneratPane = new JPanel();
			autoGeneratPane.setBorder(new EmptyBorder(5, 5, 6, 6));
			GridBagLayout autoGeneratPaneGridBagLayout = new GridBagLayout();
			autoGeneratPane.setLayout(autoGeneratPaneGridBagLayout);
			GridBagConstraints lblNewLabelGridBagConstraints = new GridBagConstraints();
			lblNewLabelGridBagConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			lblNewLabelGridBagConstraints.insets = new Insets(0, 0, 5, 5);
			lblNewLabelGridBagConstraints.gridx = 0;
			lblNewLabelGridBagConstraints.gridy = 0;
			autoGeneratPane.add(getDifficultyLabel(), lblNewLabelGridBagConstraints);
			GridBagConstraints comboBoxGridBagConstraints = new GridBagConstraints();
			comboBoxGridBagConstraints.insets = new Insets(0, 5, 5, 5);
			comboBoxGridBagConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			comboBoxGridBagConstraints.gridx = 1;
			comboBoxGridBagConstraints.gridy = 0;
			autoGeneratPane.add(getDifficultyComboBox(), comboBoxGridBagConstraints);
			GridBagConstraints gluepanel1GridBagConstraints = new GridBagConstraints();
			gluepanel1GridBagConstraints.weightx = 10.0;
			gluepanel1GridBagConstraints.insets = new Insets(0, 0, 5, 0);
			gluepanel1GridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
			gluepanel1GridBagConstraints.gridx = 2;
			gluepanel1GridBagConstraints.gridy = 0;
			autoGeneratPane.add(Box.createHorizontalGlue(), gluepanel1GridBagConstraints);
			GridBagConstraints lblNewLabel_1GridBagConstraints = new GridBagConstraints();
			lblNewLabel_1GridBagConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			lblNewLabel_1GridBagConstraints.insets = new Insets(5, 0, 0, 5);
			lblNewLabel_1GridBagConstraints.gridx = 0;
			lblNewLabel_1GridBagConstraints.gridy = 1;
			autoGeneratPane.add(getFilledCellsLabel(), lblNewLabel_1GridBagConstraints);
			GridBagConstraints spinnerGridBagConstraints = new GridBagConstraints();
			spinnerGridBagConstraints.insets = new Insets(5, 5, 0, 5);
			spinnerGridBagConstraints.anchor = GridBagConstraints.BASELINE_LEADING;
			spinnerGridBagConstraints.gridx = 1;
			spinnerGridBagConstraints.gridy = 1;
			autoGeneratPane.add(getFilledCellsSpinner(), spinnerGridBagConstraints);
			GridBagConstraints panelGridBagConstraints = new GridBagConstraints();
			panelGridBagConstraints.gridwidth = 3;
			panelGridBagConstraints.weighty = 10.0;
			panelGridBagConstraints.weightx = 10.0;
			panelGridBagConstraints.fill = GridBagConstraints.BOTH;
			panelGridBagConstraints.gridx = 0;
			panelGridBagConstraints.gridy = 2;
			autoGeneratPane.add(Box.createGlue(), panelGridBagConstraints);
		}
		return autoGeneratPane;
	}

	private SudokuBoard getMouseInputPane() {
		if (mouseInputPane == null) {
			SudokuModel model = new DefaultSudokuModel();
			model.newGame(new EmptySudokuBuilder());

			mouseInputPane = new SudokuBoard(this.application);
			mouseInputPane.setSudokuModel(model);
		}
		return mouseInputPane;
	}

	private JPanel getButtonsPane() {
		if (buttonsPane == null) {
			buttonsPane = new JPanel(new FlowLayout(FlowLayout.CENTER, 11, 0));
			buttonsPane.add(getOkButton());
			buttonsPane.add(getCancelButton());
		}
		return buttonsPane;
	}

	private JButton getOkButton() {
		if (okButton == null) {
			okButton = new JButton("OK");
			okButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					ok();
				}
			});
			okButton.setDefaultCapable(true);
			this.getRootPane().setDefaultButton(okButton);
		}
		return okButton;
	}

	private JButton getCancelButton() {
		if (cancelButton == null) {
			cancelButton = new JButton("Cancel");
			cancelButton.addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					cancel();
				}
			});
		}
		return cancelButton;
	}

	private JLabel getDifficultyLabel() {
		if (difficultyLabel == null) {
			difficultyLabel = new JLabel("Difficulty");
		}
		return difficultyLabel;
	}

	private JComboBox<Difficulty> getDifficultyComboBox() {
		if (difficultyComboBox == null) {
			Difficulty[] levels = new Difficulty[] { Difficulty.Medium, Difficulty.Hard };
			DefaultComboBoxModel<Difficulty> model = new DefaultComboBoxModel<Difficulty>(levels);
			difficultyComboBox = new JComboBox<Difficulty>(model);
		}
		return difficultyComboBox;
	}

	private JLabel getFilledCellsLabel() {
		if (filledCellsLabel == null) {
			filledCellsLabel = new JLabel("Filled Cells");
		}
		return filledCellsLabel;
	}

	private JSpinner getFilledCellsSpinner() {
		if (filledCellsSpinner == null) {
			filledCellsSpinner = new JSpinner(this.getFilledCellsSpinnerModel());
		}
		return filledCellsSpinner;
	}

	private SpinnerNumberModel getFilledCellsSpinnerModel() {
		if (null == filledCellsSpinnerModel) {
			int boxSize = this.getBoxSize();
			int gridSize = boxSize * boxSize;
			int cellsCount = gridSize * gridSize;
			filledCellsSpinnerModel = new SpinnerNumberModel(cellsCount * 2 / 5, 1, cellsCount, 1);
		}
		return filledCellsSpinnerModel;
	}

	private static final class ProxySudokuBuilder implements SudokuBuilder {

		private final SudokuBuilder delegate;
		private int boxSize = Sudokus.DEFAULT_BOX_SIZE;

		public ProxySudokuBuilder(SudokuBuilder proxy) {
			this.delegate = proxy;
		}

		public int getBoxSize() {
			return boxSize;
		}

		public void setBoxSize(int boxSize) {
			this.boxSize = boxSize;
		}

		@Override
		public void build(SudokuModel model) {
			this.delegate.build(model);
		}
	}

	private HandInputPane getHandInputPane() {
		if (handInputPane == null) {
			handInputPane = new HandInputPane();
		}
		return handInputPane;
	}
}
